#import "FinderManager.h"
#import "WorkspaceManager.h"
#import <appkit/Application.h>
#import <appkit/NXBrowser.h>
#import <appkit/NXBrowserCell.h>
#import <appkit/Text.h>
#import <appkit/Matrix.h>
#import <appkit/publicWraps.h>
#import <appkit/PopUpList.h>
#import <appkit/Matrix.h>
#import <objc/Storage.h>
#import <regex.h>
#import <strings.h>
#import <libc.h>

#define MAXFINDERCELLLEN 80
extern void NuGets() ;

@implementation FinderManager: Object
{  id finderLineTextField,
      findText,
      replaceText,
      caseSwitch,
      regExpSwitch, 
      selScopeSwitch,
      lineNumBrowser ;
}

- (int)browser:sender fillMatrix:matrix inColumn:(int)column ;
{ // delegate method for browser object 
  if([[NXApp mainWindow] isKindOf: [WorkspaceManager class]])
      return [[[NXApp mainWindow] lineList] count] ;
  return 0 ;
}

- browser:sender loadCell:cell atRow:(int)row inColumn:(int)column ;
{  if([[NXApp mainWindow] isKindOf: [WorkspaceManager class]])
   { int i,lineNum, charNum ; 
     id theView ; 
     char buf1[MAXFINDERCELLLEN+10], buf2[MAXFINDERCELLLEN + 17] ;
     theView = [[NXApp mainWindow] textView] ;   
     lineNum = * (int *) [[[NXApp mainWindow] lineList] elementAt: row] ;
     charNum = [theView positionFromLine: lineNum] ;
     [theView getSubstring: buf1 start: charNum length: MAXFINDERCELLLEN] ;
     for(i = 0 ; i < MAXFINDERCELLLEN ; i++)
     { if(buf1[i] == '\n') // don't grab more than 1 line...
       { buf1[i] = '\0' ;
         break ;
       }
     }
     buf1[i] = '\0' ; // make sure null terminated!
     sprintf(buf2,"%-5d %s",lineNum,buf1) ;
     [cell setStringValue: buf2] ;
     [cell setLeaf:YES] ;
  }
  return self ;
}

- find: (char *) text regEx: (BOOL) regEx caseSensitive: (BOOL) caseSensitive inSelection: (BOOL) inSelection ;
{ // find the next occurence of "text" in the textview of the
  // current mainWindow, provided in inherits from WorkspaceManager.  Text may contain
  // a regular expression.  The search may be case sensitive.  The search may be
  // confined to the current selection.  Returns nil if text is not found, else
  // makes the text the current selection.    
  id myWin = [lineNumBrowser window] ;
  if([[NXApp mainWindow] isKindOf: [WorkspaceManager class]])
  { int origLen, newLen ;
    long firstStart, lastStart ; 
    BOOL wrapped = NO ;
    char textBuf[1024] ;
    origLen = newLen = strlen(text) ;
    if(!regEx) 
      newLen *= 2 ; // not a rexp:leave room to quote everything
    { int i,j  ;
      char specialChars[] = "[]^*$\\." ; 
      struct regex *ex ;
      NXStream *aStream ;
      NXSelPt start, end ;
      char regexText[newLen+1] ;
      id aView ;
      if(!regEx) 
      { for(i = 0,j = 0 ; text[i] ; i++,j++)
        { if(index(specialChars,text[i])) // if a special char,
            regexText[j++] = '\\' ; // then turn off its special meaning by 
          regexText[j] = text[i] ; // quoting it.
        }
        regexText[j] = '\0' ;
      }
      else
        strcpy(regexText,text) ;
      ex = re_compile(regexText,caseSensitive) ;
      aView = [[NXApp mainWindow] textView] ;
      [aView getSel: &start :&end] ;
      if(!inSelection) // scope is entire file
      {  aStream = [[[NXApp mainWindow] textView] stream] ;
        // search begins after current selection
        firstStart = lastStart = (long) end.cp ;
      }
      else // scope is selection only
      {  int len = end.cp - start.cp ;
        { char aBuf[len + 1] ;
          aStream = NXOpenMemory(NULL, 0, NX_READWRITE) ;
          [aView getSubstring: aBuf start: start.cp length: len] ;
          NXWrite(aStream, aBuf, len) ;
          // search begins at beginning of stream
          firstStart = lastStart = 0 ;
        }
      }
      NXSeek(aStream, lastStart, NX_FROMSTART) ;
      NuGets(aStream,textBuf) ;
      do
      { if(re_match(textBuf,ex))
        { if(inSelection)
          {  [aView setSel:(int) (lastStart + ex->start -
               (int) textBuf + start.cp)
               :(int) (lastStart + ex->end)-
               (int) textBuf + start.cp] ;       
             NXCloseMemory(aStream, NX_FREEBUFFER) ;
          }
          else
            [aView setSel:(int) (lastStart + ex->start - (int) textBuf)
                       :(int) (lastStart + ex->end)- (int) textBuf] ;
          [aView scrollSelToVisible] ;
          free(ex) ;
          [[NXApp mainWindow] makeKeyAndOrderFront: self] ;
          return self ;
        }
        if(NXAtEOS(aStream)) // wrap around to beginning
        { NXSeek(aStream, 0L,NX_FROMSTART) ;
          wrapped = YES ;
        }
        lastStart = NXTell(aStream) ;
        NuGets(aStream,textBuf) ;
      } while(!(wrapped && (lastStart >= firstStart))) ;
      free(ex) ;
      if(inSelection)
         NXCloseMemory(aStream, NX_FREEBUFFER) ;
      NXBeep() ;
      [myWin setTitle: "Not Found"] ;
      NXPing() ;
      usleep(500000) ;
      [myWin setTitle: "Find"] ;
      return nil ;
    }
  }
  else
  { NXBeep() ;
    [myWin setTitle: "What window?"] ;
    NXPing() ;
    usleep(500000) ;
    [myWin setTitle: "Find"] ;
  }
  return self ;
}


- finderAddLine: sender ;
{ // add the current line to the list of lines
  if([[NXApp mainWindow] isKindOf: [WorkspaceManager class]])
  { id theView ;
    NXSelPt start, end ;
    theView = [[NXApp mainWindow] textView] ;
    [theView getSel: &start :&end] ;
    [[NXApp mainWindow] addToLineList: [theView lineFromPosition: start.cp]] ;
    [lineNumBrowser loadColumnZero] ;
  }
  return self ;
}

- finderBrowserHit: sender ;
{ NXEvent * anEvent ;
  anEvent = [NXApp currentEvent] ;
  if([[NXApp mainWindow] isKindOf: [WorkspaceManager class]])
     /* if you want to fire on double clicks, use this: 
        if([[NXApp mainWindow] isKindOf: [WorkspaceManager class]]
  	 && anEvent->data.mouse.click == 2) */
  { char aBuf[MAXFINDERCELLLEN+17] ;
    int lineNum ;
    [lineNumBrowser setPathSeparator: ' '] ;
    [lineNumBrowser getPath: aBuf toColumn: 1] ;
    if(sscanf(aBuf,"%d",&lineNum))
    { id aView ;
      aView = [[NXApp mainWindow] textView] ;
      [aView setSel: [aView positionFromLine: lineNum]
                  : [aView positionFromLine: lineNum + 1]] ;
      [aView scrollSelToVisible] ;
      [[NXApp mainWindow] makeKeyAndOrderFront: self] ;
    }
  }
  return self ;
}

- finderCurrent: sender ;
{ // put line number of beginning of selection into
  // the finderLineTextField 
  id aView ;
  if([[NXApp mainWindow] isKindOf: [WorkspaceManager class]])
  { NXSelPt start,end ;
    aView = [[NXApp mainWindow] textView] ;
    [aView getSel: &start :&end] ;
    [finderLineTextField setIntValue: [aView lineFromPosition: start.cp]] ;
  }
  return self ;
}

- finderGoTo: sender ;
{ // set selection at beginning of line indicated by
  // finderLineTextField 
  id aView ;
  if([[NXApp mainWindow] isKindOf: [WorkspaceManager class]])
  { int pos1, pos2, lineNum ;
    aView = [[NXApp mainWindow] textView] ;
    pos1 = [aView positionFromLine:lineNum = [finderLineTextField intValue]] ;
    pos2 = [aView positionFromLine: lineNum + 1] ;
    [aView setSel:pos1 :pos2] ;
    [aView scrollSelToVisible] ;
    [finderLineTextField selectText: self] ;
    [[NXApp mainWindow] makeKeyAndOrderFront: self] ;
  }
  return self ;
}

struct MenuDefs
{ @defs(Menu)
} ;

- finderNext: sender ;
{ // find the next occurence of the "find" text.
  // returns nil if unable. Note the contortions
  // needed to get the id of the current pop-up
  // list selection...the trouble is NeXT give
  // no method to obtain a Menu's Matrix
  // so have to grab it from the ivar
  struct MenuDefs *reg, *cas, *sel ;
  reg = (struct MenuDefs *) regExpSwitch ;
  cas = (struct MenuDefs *) caseSwitch ;
  sel = (struct MenuDefs *) selScopeSwitch ;
  return [self find: (char *) [findText stringValue]
    regEx: [[reg->matrix selectedCell] tag]
    caseSensitive: ![[cas->matrix selectedCell] tag]
    inSelection: [[sel->matrix selectedCell] tag]] ;
}



- finderRemLine: sender ;
{ // remove the browser-selected line from the list of lines
  if([[NXApp mainWindow] isKindOf: [WorkspaceManager class]])
  { [[NXApp mainWindow] removeFromLineList: [[lineNumBrowser matrixInColumn:0] selectedRow]] ;
    [lineNumBrowser loadColumnZero] ;
  }
  return self ;
}

- finderReplace: sender ;
{ // replace the highlighted text with the current text
  id aView ;
  if([[NXApp mainWindow] isKindOf: [WorkspaceManager class]])
  { aView = [[NXApp mainWindow] textView] ;
    [aView replaceSel: [replaceText stringValue]] ;
  }
  return self ;
}

- finderReplaceAll: sender ;
{ // replace all occurences of the find text with the replace text
  if([[NXApp mainWindow] isKindOf: [WorkspaceManager class]])
  { struct MenuDefs *sel ;
    sel = (struct MenuDefs *) selScopeSwitch ;
    if(![[sel->matrix selectedCell] tag])
    { if([self finderNext: self])
       while([self finderReplaceAndFind: self]) ;
    }
    else // scope is entire file
    { NXSelPt start, end ;
      id aView ;
      int diff ;
      diff = strlen([replaceText stringValue]) -
             strlen([findText stringValue]) ; 
      aView = [[NXApp mainWindow] textView] ;
      [aView getSel:&start :&end] ;
      while([self finderNext: self])
      { end.cp += diff ;
        [self finderReplace: self] ;
        // must reset the (adjusted) selection
        [aView setSel: start.cp :end.cp] ;        
      }
    }
  }
  return self ;
  
}

- finderReplaceAndFind: sender ;
{ // replace the highlighted text with the replacement text,
  // find next occurence of find text
  if([[NXApp mainWindow] isKindOf: [WorkspaceManager class]])
  { [self finderReplace: self] ;
    return [self finderNext: self] ;
  }
  return self ;
}

- findLine ;
{ return finderLineTextField ;
}

- findText ;
{ return findText ;
}

- init ;
{ // IB sends us our popupList's trigger id, not the popupLists
  // themselves
  [super init] ;
  caseSwitch = [caseSwitch target] ;
  regExpSwitch = [regExpSwitch target] ;
  selScopeSwitch = [selScopeSwitch target] ;
  return self ;
}

- lineNumBrowser ;
{ return lineNumBrowser ;
}

- setTheFindText: (char *) theText ;
{ // set the find text to theText
  [findText setStringValue: theText] ;
  return self ;
}

@end
